package Puyo;
/*
 * PuyoLogic.java
 *
 * Created on August 2,2004, PM 8:50
 */

import java.util.*;
import java.awt.*;
import javax.swing.*;

/**
 * Game data , game logic and the core algorihtm for puyo games
 * @author  Ming
 */

/**
 * Class for dropping balls
 */
class Ball { 
   /**ball X position*/
   public int x;
   /**ball Y position*/
   public int y;   
   /**ball colors*/
   public int color;
   /**indicate if this ball has been dropped to the ball stack*/
   public boolean dropped;
}

/*
 * class to encapsulate the puyo game logic.
 */
public class PuyoLogic{
    /**the number of balls everytime falling from above. Currently 2*/
    public static final int numDroppingBalls=2;
    
    /**ball color enumerate: Empty to represent free space*/
    public static final int puyoEmpty=0;
    /**ball color enumerate: Red*/
    public static final int puyoRed=1;
    /**ball color enumerate: Green*/
    public static final int puyoGreen=2;
    /**ball color enumerate: Blue*/
    public static final int puyoBule=3;
    /**ball color enumerate: Yellow*/
    public static final int puyoYellow=4;
    /**ball color enumerate: Special, placed around the puyo board to simplify boundary condition problems*/
    public static final int puyoSpecial=5;
  
    /**game score, rule:  score+=4+(connCompCounter-4)*(connCompCounter-4); */
    int score = 0;
    
    /**game state control*/
    boolean running = false;
    
    /**game state control*/
    boolean paused = false;
    
    /**game speed control*/
    int timeInterval = 500;
    /**speed change rate control*/
    double speedChangeRate = 1.25;
  
    //////////////game data////////////
    /**dropping balls*/
    public Ball droppingBalls[];
    /**accumulated balls in the box*/
    public int accumBalls[][];
    
    /**for random ball color generation*/
    Random rand;
    
    /////////for connected component search//////
    /**Flag matrix*/
    boolean flagMatrix[][];
    
    /**Connected component counter. Also known as connectivity number*/
    int connCompCounter;
    
    int boardWidth;
    int boardHeight;
    
    /** Creates a new instance of PuyoLogic */
    public PuyoLogic(int width, int height) {
        boardWidth = width;
        boardHeight = height;
        initAll();        
    }
    
    /**all initialization work for the game data*/
    public void initAll(){        
        //Additional 2 grids at the board to simplify the boundary condition problem
        accumBalls= new int[boardWidth+2][boardHeight+2];
        flagMatrix= new boolean[boardWidth+2][boardHeight+2];
        for(int i=0;i<boardWidth+2;i++)
            for(int j=0;j<boardHeight+2;j++){
                if(i==0 || j==0 || i==boardWidth+1 || j==boardHeight+1){
                    accumBalls[i][j]= puyoSpecial;
                    flagMatrix[i][j]=true;
                }
                else {
                    accumBalls[i][j]= puyoEmpty;
                    flagMatrix[i][j]=false;
                }                
            }
        rand=new Random(System.currentTimeMillis());  
        initDroppingBalls();
        score=0;
    }
    
    /**initialize game data related to the dropping balls*/
    public boolean initDroppingBalls(){
       droppingBalls = new Ball[numDroppingBalls];
       for(int i=0;i<numDroppingBalls;i++){
           droppingBalls[i]=new Ball();
           
           //might go beyond the boundary of the width if numDroppingBalls is too large         
           droppingBalls[i].x=boardWidth/2+i; 
           droppingBalls[i].y=boardHeight;
           
           if(accumBalls[droppingBalls[i].x][droppingBalls[i].y] != puyoEmpty)
               return false;
           //generate a color between 1-4: red, green, blue, yellow
           droppingBalls[i].color=rand.nextInt(4)+1; 
           droppingBalls[i].dropped=false;
       }
       return true;
    }
    
    /**Drop balls one step. If a ball hits on the stack, we should drop the others automatically*/
    public boolean drop(){   
       boolean dropped=false; //indicate one ball has hit one of the accumulated balls
       for(int i=0;i<numDroppingBalls;i++){
         if(accumBalls[droppingBalls[i].x][droppingBalls[i].y-1] !=  puyoEmpty){
             accumBalls[droppingBalls[i].x][droppingBalls[i].y]=droppingBalls[i].color;
             droppingBalls[i].dropped=true;
             dropped=true;
             break;
         }
       }       
     
       if(dropped){   //one ball has been dropped, we automatically drop the others
           int numDroppedBalls=0;
           while(numDroppedBalls!=numDroppingBalls){
             numDroppedBalls=0;
             for(int i=0;i<numDroppingBalls;i++){
                if(droppingBalls[i].dropped) {
                    numDroppedBalls++;
                    continue;
                }
                if(accumBalls[droppingBalls[i].x][droppingBalls[i].y-1] !=  puyoEmpty){
                    accumBalls[droppingBalls[i].x][droppingBalls[i].y]=droppingBalls[i].color;
                    droppingBalls[i].dropped=true;
                }  else  droppingBalls[i].y-=1;
             }
           }
       }else{ //otherwise, drop all balls
           for(int i=0;i<numDroppingBalls;i++) 
              droppingBalls[i].y-=1;
       }      
       return dropped;
    }
   
    /**Set connected component to empty. i.e. remove the connected puyos*/
     private void removeConnComp(int x, int y, int color){
         if(accumBalls[x][y] != color )
            return; 
         //System.out.println("Empty: "+x+" "+y);
         accumBalls[x][y]=puyoEmpty;
         removeConnComp(x,y-1,color);
         removeConnComp(x,y+1,color);
         removeConnComp(x-1,y,color);
         removeConnComp(x+1,y,color);             
    }
     
    /**check connected component, generate the connectivity number */
    public void checkConnComp(int x, int y, int color){
        if(accumBalls[x][y] != color)
            return;
        connCompCounter++;
        flagMatrix[x][y]=true;
        if(!flagMatrix[x][y-1]) checkConnComp(x,y-1,color);
        if(!flagMatrix[x][y+1]) checkConnComp(x,y+1,color);
        if(!flagMatrix[x-1][y]) checkConnComp(x-1,y,color);
        if(!flagMatrix[x+1][y]) checkConnComp(x+1,y,color);   
            
    }
   
    /**Check the connected component and destroy them recursively*/
    public boolean checkAccumBalls(){
      boolean removed=false;
      int m,k;
      for(int i=1;i<boardWidth+1;i++)
         for(int j=1;j<boardHeight+1;j++){
             connCompCounter=0;
             for(m=1;m<boardWidth+1;m++)
                for(k=1;k<boardHeight+1;k++)
                    flagMatrix[m][k]=false;
             if(accumBalls[i][j]!=puyoEmpty && !flagMatrix[i][j]){
                checkConnComp(i,j,accumBalls[i][j]);               
                //System.out.println("Connected Component Number: "+ connCompCounter + "; Color: "+accumBalls[i][j]);
                if(connCompCounter>=4){                   
                   removeConnComp(i,j,accumBalls[i][j]);
                   score+=4+(connCompCounter-4)*(connCompCounter-4);
                   removed=true;
                }
             }
         }
      return removed;
    }
    
    /**Once some balls are removed, we should let the others perform free-falling!
     * one grid falling at one time
     */
    public boolean freeFalling(){        
        int i,j;
        boolean unfinished=false;
        for(i=1;i<boardWidth+1;i++){
            int indexBottom=0, indexTop=0;
            for(j=1;j<boardHeight+1;j++)
                if(accumBalls[i][j] == puyoEmpty){
                    indexBottom=j;  
                    break;
                }
            for(j=boardHeight;j>0;j--){
                if(accumBalls[i][j]!=puyoEmpty){
                    indexTop=j; 
                    break;
                }  
            } 
            //System.out.println("column "+i+": "+indexBottom+" "+indexTop);
            if(indexTop <= indexBottom)                  
                continue;
            for(j=indexBottom;j<indexTop;j++){
                accumBalls[i][j]=accumBalls[i][j+1];
            }
            accumBalls[i][indexTop]=puyoEmpty;
            unfinished=true; 
        }
        return unfinished;
    }
    
    /**pause switch*/
    public void changePauseState(){
       paused = !paused;
    }  
    
    /**tune up game speed*/
    public void speedUp(){
       timeInterval /= speedChangeRate;
    }
    
    /**tune down game speed*/
    public void speedDown(){
       timeInterval *= speedChangeRate;
    }
    
    /**move dropping balls one step left*/
    public void moveLeft(){
      int leftMost=Integer.MAX_VALUE;
      for(int i=0;i<numDroppingBalls;i++){
        if(droppingBalls[i].dropped) continue;
        if(droppingBalls[i].x < leftMost)
            leftMost = droppingBalls[i].x;       
      }
      for(int i=0;i<numDroppingBalls;i++){
         if(droppingBalls[i].dropped) continue;      
         if(accumBalls[leftMost-1][droppingBalls[i].y] ==  puyoEmpty)
             droppingBalls[i].x-=1;             
       }
    }
   
    /**move dropping balls one step right*/
    public void moveRight() {
      int rightMost=Integer.MIN_VALUE;
      for(int i=0;i<numDroppingBalls;i++){
        if(droppingBalls[i].dropped) continue;
        if(droppingBalls[i].x > rightMost)
            rightMost = droppingBalls[i].x;       
      }
      for(int i=0;i<numDroppingBalls;i++){
         if(droppingBalls[i].dropped) continue;      
         if(accumBalls[rightMost+1][droppingBalls[i].y] ==  puyoEmpty)
             droppingBalls[i].x+=1;             
       }
    }    
   
   /**
     *use the last ball in the dropping ball list as pivot
     *NOTE: This implementation is only valid for the 2-ball case
    */
    public void rotateCW(){
        //  1       
        //  2   ->  2  1      
       if(droppingBalls[0].x == droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y > droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[numDroppingBalls-1].x+1][droppingBalls[numDroppingBalls-1].y] == puyoEmpty ) {
                droppingBalls[0].x+=1;
                droppingBalls[0].y-=1;
            }
            return;
       }
        //  2  1 -> 2       
        //          1
       if(droppingBalls[0].x > droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y == droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[numDroppingBalls-1].x][droppingBalls[numDroppingBalls-1].y-1] == puyoEmpty ) {
                droppingBalls[0].x-=1;
                droppingBalls[0].y-=1;
            }
            return;
       }
        //  2  ->  1  2       
        //  1        
       if(droppingBalls[0].x == droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y < droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[numDroppingBalls-1].x-1][droppingBalls[numDroppingBalls-1].y] == puyoEmpty ) {
                droppingBalls[0].x-=1;
                droppingBalls[0].y+=1;
            }
            return;
       }
        //             1         
        //   1  2  ->  2   
       if(droppingBalls[0].x < droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y == droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[numDroppingBalls-1].x][droppingBalls[numDroppingBalls-1].y+1] == puyoEmpty ) {
                droppingBalls[0].x+=1;
                droppingBalls[0].y+=1;
            }
            return;
       }  
       
    }
    
    /**
     *use the first ball in the dropping ball list as pivot
     *NOTE: This implementation is only valid for the 2-ball case
    */
    public void rotateCCW(){
        //  1   ->  2  1    
        //  2        
       if(droppingBalls[0].x == droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y > droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[0].x-1][droppingBalls[0].y] == puyoEmpty ) {
                droppingBalls[numDroppingBalls-1].x-=1;
                droppingBalls[numDroppingBalls-1].y+=1;
            }
            return;
       }
        //           2       
        //  2  1 ->  1
       if(droppingBalls[0].x > droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y == droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[0].x][droppingBalls[0].y+1] == puyoEmpty ) {
                droppingBalls[numDroppingBalls-1].x+=1;
                droppingBalls[numDroppingBalls-1].y+=1;
            }
            return;
       }
        //  2         
        //  1  ->  1  2      
       if(droppingBalls[0].x == droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y < droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[0].x+1][droppingBalls[0].y] == puyoEmpty ) {
                droppingBalls[numDroppingBalls-1].x+=1;
                droppingBalls[numDroppingBalls-1].y-=1;
            }
            return;
       }
        //   1  2 ->   1         
        //             2   
       if(droppingBalls[0].x < droppingBalls[numDroppingBalls-1].x &&
          droppingBalls[0].y == droppingBalls[numDroppingBalls-1].y){
            if(accumBalls[droppingBalls[0].x][droppingBalls[0].y-1] == puyoEmpty ) {
                droppingBalls[numDroppingBalls-1].x-=1;
                droppingBalls[numDroppingBalls-1].y-=1;
            }
            return;
       }  
       
    }
    
   

    public int getTimeDelay() {
        return timeInterval;
    }

    public boolean isPaused() {
        return paused;
    }

    public boolean isRunning() {
        return running;
    }
}
